///////////////////////////////////////////////////////////////////////////////////////////////////
// Illusion Ray Development Kit (IDK)           Copyright (c) 2010 ( dimitriy.martynenko@gmail.com )
///////////////////////////////////////////////////////////////////////////////////////////////////
// Created : 2010-05-22
// Updated : ...
// Licence : NEW BSD
// Info	   : IDK Engine
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma once

#include <IDK.hpp>
#include <SceneNode.hpp>

typedef TTreeListItem<SceneNode> TTreeListSceneNode;

typedef List<SceneNode, TTreeListSceneNode> SceneNodeList;

typedef SceneNodeList::Instance TSceneBase;

class TScene : public TSceneBase
{
protected://Find helper classes
	class TFindByName : public TComparer<ListItem, String>
	{
		typedef TComparer<ListItem, String> Base;
	public:
		TFindByName(String cmp) : Base(cmp)
		{}

		inline virtual bool Compare(ListItem& src) const
		{
			return mCompare == src->Current()->GetName();
		}
	};

	class TFindByNode : public TComparer<ListItem, ref<TSceneNode> >
	{
		typedef TComparer<ListItem, ref<TSceneNode> > Base;
	public:
		TFindByNode(ref<TSceneNode> cmp) : Base(cmp)
		{}

		inline virtual bool Compare(ListItem& src) const
		{
			return mCompare.Equal(src->Current());
		}
	};

	class TFindByRayCast : public TComparer<ListItem, Math::Ray>
	{
		typedef TComparer<ListItem, Math::Ray> Base;
	private:
		mutable ListItem mResult;
		mutable float mResultDistance;
	public:
		TFindByRayCast(const Math::Ray& ray) : Base(ray),
			mResultDistance(Math::Infinity<float>())
		{
		}

		inline ListItem Result(void) const
		{
			return mResult;
		}

		inline virtual bool Compare(ListItem& src) const
		{

			const Math::AABB& aabb = src->Current()->GetTransformAABBox();

			if(!aabb.IsInfinity())
			{
				float dist = 0.0f;

				iLog(src->Current()->GetName()->Chars());
				if(mCompare.Intersect(aabb, dist))
				{
					if(dist < mResultDistance)
					{
						mResult = src;
						mResultDistance = dist;
					}
				}
			}

			return false;
		}
	};

protected:
	typedef ref<IComparer<ListItem> > ListItemComparer;

	bool FindNodeItemsWithResult(ListItem iterator, ListItemComparer& comparer, ListItem& result)
	{
		if(comparer->Compare(iterator))
		{
			result = iterator;
			return false;
		}

		for(Iterator it = iterator->GetChildren()->Begin(),
			end = iterator->GetChildren()->End();
			it!=end;
		++it)
		{
			if(!FindNodeItemsWithResult(*it, comparer, result))
			{
				return false;
			}
		}
		return true;
	}

	//copy past(upper clone) : no time wit out result out
	bool FindNodeItems(ListItem iterator, ListItemComparer& comparer)
	{
		if(comparer->Compare(iterator))
		{
			return false;
		}

		for(Iterator it = iterator->GetChildren()->Begin(),
			end = iterator->GetChildren()->End();
			it!=end;
		++it)
		{
			if(!FindNodeItems(*it, comparer))
			{
				return false;
			}
		}
		return true;
	}

	ListItem FindNode(ListItemComparer comparer, bool prompt = true)
	{
		ListItem retValue = NullPtr;
		//
		for(Iterator it = Begin(), end = End(); it!=end; ++it)
		{
			if(!FindNodeItemsWithResult(*it, comparer, retValue))
			{
				return retValue;
			}
		}

		if(prompt)
		{
			iError("Can't find node");
		}

		return NullPtr;
	}
public:
	TScene(void) : TSceneBase()
	{
	}
public:
	ref<TSceneNode> AddTo(ref<TSceneNode> node, ref<TSceneNode> parent = NullPtr)
	{
		if(parent != NullPtr)
		{
			FindNode(new TFindByNode(parent))->GetChildren()->Add(node);
		}
		else // add to root
		{
			Add(node);
		}
		return node;
	}

	SceneNodeList GetRootNodes()
	{
		return SceneNodeList(this);
	}

	ref<TSceneNode> GetNodeByRay(ref<TSceneNode> container, const System::Math::Ray& ray)
	{
		ref<TFindByRayCast> finder = new TFindByRayCast(ray);

		ListItem foundItem = FindNode(new TFindByNode(container), false);

		ListItemComparer temp = finder.As<ListItemComparer::Instance>();

		FindNodeItems(foundItem, temp);

		//FindNode(finder.As<IComparer<ListItem>>(), false);

		ListItem findResult = finder->Result();

		if(findResult == NullPtr)
		{
			return NullPtr; 
		}
		else
		{
			return findResult->Current();
		}
	}

	ref<TSceneNode> GetNode(const String& name)
	{
		ListItem result = FindNode(new TFindByName(name));
		return result->Current();
	}

	void UpdateTransform()
	{
		//need re-calculate scene transform
		ListItem nullable = NullPtr;

		for(Iterator it = Begin(), end = End(); it!=end; ++it)
		{
			OnUpdateItems(*it, nullable, false);
		}
	}
public:
	virtual void Rendered(IDK::IRenderState& /* rndState*/)
	{
		//
	}

	void OnUpdateItems(ListItem& iterator, ListItem& parent, bool parentUpdate)
	{
		if(parent != NullPtr)
		{
			parentUpdate = iterator->Current()->Update(parent->Current(), parentUpdate);
		}
		else
		{
			parentUpdate = iterator->Current()->Update(NullPtr, false);
		}

		for(Iterator it = iterator->GetChildren()->Begin(),
			end = iterator->GetChildren()->End();
			it!=end; 
		++it)
		{
			OnUpdateItems(*it, iterator, parentUpdate);
		}
	}

	void DrawItems(ListItem& iterator, IDK::IRenderState& rndState)
	{
		if(iterator->Current()->Render(rndState))
		{
			for(Iterator it = iterator->GetChildren()->Begin(),
				end = iterator->GetChildren()->End(); it!=end; ++it)
			{
				DrawItems(*it, rndState);
			}

			iterator->Current()->Rendered(rndState);
		}
	}

	virtual bool Render(IDK::IRenderState& rndState)
	{
		//need calculate scene transform
		for(Iterator it = Begin(), end = End(); it!=end; ++it)
		{
			DrawItems(*it, rndState);
		}
		Rendered(rndState);

		return true;
	}
};

typedef ref<TScene> Scene;